#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <poll.h>
#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"
#include "write/afile.h"
#include "common.h"

#ifndef NAMEMAX
#define NAMEMAX 10
#endif
#ifndef BSIZE
#define BSIZE 0x1000
#endif
#ifdef SIGINFO
#define IFINFO(x) (x)
#else
#define IFINFO(x)
#endif

static sigset_t sigpend;
#ifdef SIGINFO
static size_t info_bytes = 0;
#endif

static void	sighandle(int);
#ifdef SIGINFO
static void	siginfo(int);
#endif

int
main(int argc, char *argv[])
{
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
#ifdef USE_BITRATE
	uint32_t bitrate = 0;
#endif
#ifdef USE_MODE
	char *amode = NULL;
#endif
#ifdef USE_QUALITY
	double quality = 1;
#endif
	int cpipe[2];
	pid_t cpid = 0;
	uint32_t enc;
	struct au au = AU_INITIALIZER;
	struct afile *a;
	ssize_t i;
	ssize_t r;
	size_t bsize = BSIZE;
	void *b;
	char *p;
	int c;

	setprogname(argv[0]);
	while ((c = getopt(argc, argv, "B:C:I:RWb:c:i:m:q:")) != -1)
		switch (c) {
		case 'B':
#ifdef USE_BITRATE
			bitrate = strtou32(optarg);
#endif
			break;
		case 'b':
			bsize = strtoul(optarg, &p, 0);
			if (p == optarg || *p != '\0')
				errx(EX_USAGE, "Invalid number '%s'",
				    optarg);
			if (bsize < 0x400)
				errx(EX_USAGE, "The buffer size (%zu) "
				    "is too small", bsize);
			break;
		case 'm':
#ifdef USE_MODE
			amode = optarg;
#endif
			break;
		case 'q':
#ifdef USE_QUALITY
			quality = strtod(optarg, &p);
			if (p == optarg || *p != '\0')
				errx(EX_USAGE, "Invalid number '%s'",
				    optarg);
#endif
			break;
		default:
			goto usage;
		}
	if ((b = malloc(bsize)) == NULL)
		err(EX_OSERR, "Could not allocate the buffer");
	sigemptyset(&sigpend);
	(void)signal(SIGINT, &sighandle);
	IFINFO((void)signal(SIGINFO, &siginfo));
#ifdef USE_PLEDGE
	pledge("stdio proc exec", NULL);
	c = 1;
#endif
#ifdef USE_CAPSICUM
	c = 1;
#endif
	if ((a = af_fdopen(STDOUT_FILENO)) == NULL)
		err(EX_DATAERR, "Could not initialize audio codec");
#ifdef USE_MODE
	if (amode != NULL && af_set(a, "mode", amode) != 1) {
		fprintf(stderr, "%s: Invalid mode %s\n", amode);
		if (af_get(a, "mode", &amode) == 1)
			fprintf(stderr, "Available modes: %s\n", amode);
		exit(EX_USAGE);
	}
#endif
#ifdef USE_BITRATE
	(void)af_set(a, "bitrate", bitrate);
#endif
#ifdef USE_QUALITY
	(void)af_set(a, "quality", quality);
#endif
	while (nread(STDIN_FILENO, b, AU_SIZE) == AU_SIZE) {
		IFINFO(info_bytes += AU_SIZE);
		if (au_gethdr(&au, b) != 0)
			errx(EX_DATAERR, "Bad header");
		if (af_set(a, "rate channels", au.au_rate, au.au_chan)
		    != 2)
			err(EX_DATAERR,
			    "Could not set audio parameters");
		if (af_set(a, "encoding", au.au_enc) != 1) {
			if (cpid > 0)
				abort();
			if (af_get(a, "encoding", &enc) != 1)
				err(EX_SOFTWARE,
				    "Could not get desired encoding");
			if (pipe(cpipe) == -1)
				err(EX_OSERR, "Could not create pipe");
			switch (cpid = fork()) {
			case -1:
				err(EX_OSERR, "Could not fork process");
			case 0:
				close(cpipe[0]);
				if (dup2(cpipe[1], STDOUT_FILENO) == -1)
					err(EX_OSERR, "Could not "
					    "connect the pipe");
				auconvp(&au, enc, b, bsize);
				err(EX_SOFTWARE,
				    "Could not execute auconv");
			default:
				close(cpipe[1]);
				if (dup2(cpipe[0], STDIN_FILENO) == -1)
					err(EX_OSERR, "Could not "
					    "connect the pipe");
				break;
			}
			continue;
		}
#ifdef USE_CAPSICUM
		if (c) {
			if (cap_enter() == -1 && errno != ENOSYS)
				err(EX_OSERR,
				    "Could not enter capability mode");
			(void)cap_rights_init(&rights, CAP_READ);
			if (cap_rights_limit(STDIN_FILENO, &rights)
			    != 0 && errno != ENOSYS)
				err(EX_OSERR, "Could not limit the "
				    "standard input");
			(void)cap_rights_init(&rights, CAP_WRITE,
			    CAP_SEEK);
			if (cap_rights_limit(STDOUT_FILENO, &rights)
			    != 0 && errno != ENOSYS)
				err(EX_OSERR, "Could not limit the "
				    "standard output");
			(void)cap_rights_init(&rights, CAP_WRITE);
			if (cap_rights_limit(STDERR_FILENO, &rights)
			    != 0 && errno != ENOSYS)
				err(EX_OSERR, "Could not limit the "
				    "standard error");
			c = 0;
		}
#endif /* USE_CAPSICUM */
#ifdef USE_PLEDGE
		if (c) {
			pledge("stdio", NULL);
			c = 0;
		}
#endif
		for (i = AU_SIZE; i < au.au_off; i += r) {
			if ((r = read(STDIN_FILENO, b,
			    MIN(bsize, au.au_off - i))) < 0) {
				if (errno == EINTR)
					goto ret;
				err(EX_IOERR, "read error");
			}
			if (r == 0)
				err(EX_DATAERR, "Unexpected EOF");
			IFINFO(info_bytes += r);
		}
		while ((r = read(STDIN_FILENO, b,
		    MIN(bsize, au.au_size))) > 0) {
			if (af_write(a, b, r) != r) {
				if (errno == EINTR)
					goto ret;
				err(EX_IOERR, "write error");
			}
			if (au.au_size != UINT32_MAX)
				au.au_size -= r;
			IFINFO(info_bytes += r);
		}
		if (r < 0) {
			if (errno == EINTR)
				goto ret;
			err(EX_IOERR, "read error");
		}
		if (r == 0) { /* EOF */
		ret:
			if (au.au_size > 0 &&
			    au.au_size != UINT32_MAX) {
				af_close(a);
				free(b);
				err(EX_DATAERR, "Unexpected EOF");
			}
			break;
		}
	}
	af_close(a);
	free(b);
	return (EX_OK);
usage:
	c = strlen(getprogname()) + 8;
	(void)fprintf(stderr, "usage: %s"
#ifdef USE_BITRATE
	    " [-B bitrate]"
#endif
	    " [-b buffer-size]"
#ifdef USE_MODE
	    " [-m mode]"
#endif
#ifdef USE_QUALITY
	    " [-q quality]"
#endif
	    "\n", getprogname());
	return (EX_USAGE);
}

void
sighandle(int sig)
{

	(void)sigaddset(&sigpend, sig);
}

#ifdef SIGINFO
void
siginfo(int sig)
{
	/* au2XXXX: XXXXXXiB processed */
	char b[NAMEMAX + 2 + 8 + 11];
	size_t i;

	i = strlen(getprogname());
	if (i > NAMEMAX) {
		(void)memcpy(b, getprogname(), NAMEMAX - 3);
		(void)memcpy(b + NAMEMAX - 3, "...", 3);
		i = NAMEMAX;
	} else
		(void)memcpy(b, getprogname(), i);
	(void)memcpy(b + i, ": ", 2);
	i += 2;
	i += szwrite(b + i, info_bytes, "B", "iB");
	(void)memcpy(b + i, " processed\n", 11);
	i += 11;
	(void)write(STDERR_FILENO, b, i);
}
#endif
